package edu.ntu.cloudroid.model.data;

import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;

import com.google.appengine.api.datastore.Key;

import javax.jdo.annotations.FetchGroup;
import javax.jdo.annotations.Inheritance;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;


import com.sun.xacml.attr.DoubleAttribute;
import com.sun.xacml.attr.StringAttribute;
import com.sun.xacml.ctx.Attribute;
import com.sun.xacml.ctx.RequestCtx;
import com.sun.xacml.ctx.Subject;

import edu.ntu.cloudroid.model.IDs;
import edu.ntu.cloudroid.model.profile.ProfileData;
import edu.ntu.cloudroid.server.backendservice.ProfileService;
import edu.ntu.cloudroid.xacml.XACMLStrings;

@PersistenceCapable(detachable = "true")
@Inheritance
@FetchGroup(name = AbstractData.FETCH_GROUP_NAME, members = { @Persistent(name = "person") })
public class LocationData extends AbstractData {

	private static final String NODE_NAME_TYPE = "type";
	private static final String NODE_NAME_FORMATTED_ADDRESS = "formatted_address";
	private static final String NODE_TYPE_STREET_ADDRESS = "street_address"; // level 0
	private static final String NODE_TYPE_NEIGHBORHOOD = "neighborhood"; // level 1	
	private static final String NODE_TYPE_COUNTRY = "country"; // level 2
	   
	private static final String serviceURL = "http://maps.googleapis.com/maps/api/geocode/xml?latlng=%s&sensor=true";

	@Persistent
	private double latitude; //null value = -1

	@Persistent
	private double longitude; // null value = -1

	@Persistent
	private String localtionName; //places, building, etc ...
	
	public LocationData() {

	}

	public LocationData(String name, double lot, double lat, String locName, Date ts)
			throws Exception {
		//this.setProfile(ProfileService.getProfile(name));
		this.setName(name);
		ProfileData p=null;
		if (!name.equals("")){
			this.setName(name);
		}
		this.setLongitude(lot);
		this.setLatitude(lat);
		this.setTimestamp(ts);
		this.localtionName = locName;
	}

	public void setLocationName(String n){
		this.localtionName = n;
	}
	
	public String getLocationName(){
		return this.localtionName;
	}
	
	public double getLongitude() {
		return this.longitude;
	}

	public double getLatitude() {
		return this.latitude;
	}

	public void setLatitude(double latitude) {
		this.latitude = latitude;
	}

	public void setLongitude(double longitude) {
		this.longitude = longitude;
	}

	@Override
	public DetachedData detach(Object params) {
		//return every thing
		return new LocationInfo(this.getName(), this.getLongitude(),
				this.getLatitude(), this.getLocationName(), this.getTimestamp());
	}

	@Override
	public int compareTo(String v, int idx) {
		return 0;
	}
		

	@Override
	public AbstractData initialize(String[] entries) throws Exception {
		// the format will be <data type> <name> <longitude> <lattitude> <date>
		LocationData ld = new LocationData();
		ld.setLongitude(new Double(entries[2]).doubleValue());
		ld.setLatitude(new Double(entries[3]).doubleValue());
		ld.setLocationName(entries[4]);
		try {
			//ld.setProfile(ProfileService.getProfile(entries[1]));
			ld.setName(entries[1]);
			ld.setTimestamp(new SimpleDateFormat("yyyy-MM-dd HH:mm")
					.parse(entries[5]));			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return ld;
	}

	@Override
	public Object getWindowAverage(List<AbstractData> list, int idx) {		
		return null;//nothing to summary here
	}

	@Override
	public AbstractData getInstance() {
		return new LocationData();
	}

	@Override
	public int compareTo(AbstractData v, int idx) {
		LocationData ld = (LocationData) v;
		if ((Math.abs(ld.getLatitude() - this.latitude) < 0.001)
				&& (Math.abs(ld.getLongitude() - this.longitude) < 0.001))
			return 0;
		else
			return 1;
	}

	//changing the current location to a different granuality
	//the current granuality is 0. Maxium of 4 level. Returning a string
	@Override
	public Object changeGranuality(int granuality, int colIdx) throws Exception {
		String latlngString = this.latitude + "," + this.longitude;

		URL url = new URL(String.format(serviceURL, latlngString));
		DocumentBuilder builder = DocumentBuilderFactory.newInstance()
				.newDocumentBuilder();

		InputSource source = new InputSource(url.openStream());
		Document doc = builder.parse(source);
		NodeList nl = doc.getDocumentElement().getElementsByTagName("result");

		String expectedType = "";
		switch (granuality) {
		case 0:
			expectedType = NODE_TYPE_STREET_ADDRESS;
			break;
		case 1:
			expectedType = NODE_TYPE_NEIGHBORHOOD;
			break;
		case 2:
			expectedType = NODE_TYPE_COUNTRY;
			break;
		}
	
		String placeName = "";
		for (int i = 0; i < nl.getLength(); i++) {
			Node n = nl.item(i);
			NodeList children = n.getChildNodes();

			boolean resultMatches = false;
			for (int j = 0; j < children.getLength(); j++) {						
				if (children.item(j).getNodeName().equals(NODE_NAME_TYPE)) {					
					if (children.item(j).getFirstChild().getNodeValue()
							.equals(expectedType)) {
						resultMatches = true;
					}
				}
			}

			if (resultMatches) {
				for (int j = 0; j < children.getLength(); j++) {			
					if (children.item(j).getNodeName()
							.equals(NODE_NAME_FORMATTED_ADDRESS)) {						
						placeName = children.item(j).getFirstChild()
								.getNodeValue();
						break;
					}
				}
			}

			if (!placeName.equals("")) {
				break;
			}
		}
		return new LocationInfo(this.getName(), -1, -1, placeName,
				this.getTimestamp());
	}

	//when there's input, reconstruct a object
	@Override
	public AbstractData getDataFromRequest(RequestCtx requestCtx) throws Exception{		
		Subject subject = (Subject)requestCtx.getSubjects().iterator().next();
		Attribute att;
		double longitude=0, lattitude=0;
		for (Object obj : subject.getAttributes()){
			att = (Attribute) obj;
			if (att.getId().equals(new URI(XACMLStrings.ATTR_Latitude)))
				lattitude = ((DoubleAttribute)att.getValue()).getValue();
			else if (att.getId().equals(new URI(XACMLStrings.ATTR_Longitude))){
				longitude = ((DoubleAttribute)att.getValue()).getValue();
			}			
		}
		return new LocationData("", longitude, lattitude, "", new Date());
	}	

	@Override
	public int getDataId(){
		return IDs.DataID.LocationData.ordinal();
	}
}
